/*
 file: class.js
 dependsOn: core.js
 */

// Add some properties to the core object
$.extend($, 
{
  /**
   * Builds an Array from an iterable (an object with a length property and
   * an indexer, such as NodeList or arguments) or from an object with a
   * toArray method. If anything else is provided, an empty array is returned.
   * @alias $.A
   * @param {Object} object The object to build an Array from
   * @return {Array} The Array containing the elements in the iterable or
   *  object, or an empty Array if conversion wasn't possible.
   * @since 0.1
   */
  toArray: function(object) 
  {
    if (object == null) return [];
    if (object.toArray) return object.toArray();
    if (isNaN(object.length)) return [object];
    
    var length = object.length, results = [];    
    while (length--) results[length] = object[length];      
    return results;
  },
  
  /**
   * Adds methods to destination's prototype from source.
   * @alias $.addMethods
   * @param {Object} destination The object whose prototype should be extended
   * @param {Object} source      The object with the methods to add
   * @param {Object} overwrite   Flags whether to overwrite existing methods in
   *  destination's prototype with those in source
   * @since 0.1
   */
  addMethods: function(destination, source, overwrite) 
  {
    for (var property in source) 
      if ((!(property in destination.prototype) || overwrite) && 
          typeof source[property] == 'function') 
        destination.prototype[property] = source[property];   
        
    return destination;
  },
  
  /**
   * Tests whether the passed object is an instance (or sublcass) of
   * the class provided. Some examples:
   *   isA(x, Object) returns true if x isn't null, undefined or a primitive.
   *   isA(x, Number) returns true if x is a Number or a primitive number.
   *   isA(x, Car)    returns true if x is a Car or any subclass of Car.
   * @alias $.isA
   * @param  {Object}  object The object to test
   * @param  {Object}  object The class to check against
   * @return {Boolean} True if the object is an instance (or sublcass) of
   *  the class provided, false otherwise.
   * @since 0.1
   */
  isA: function(object, type) 
  {
    if(object == null)
      return false;
    
    var ctor = object.constructor;
    
    do {
      if(ctor === type)
        return true;
    } while(ctor = ctor.superclass);
    
    return false;
  },

  /**
   * Creates a new class from the definition provided.
   * @alias $.newClass
   * @param {Object} definition The class definition, which should be an 
   *  object containing the methods for this class as properties, as well as
   *  a reference to the superclass in a property named superclass (should
   *  inheritence be desired). If a superclass is specified and any of the 
   *  methods in the definition already exist in the superclass, they will be 
   *  overwritten and the original method in the superclass can be invoked
   *  by calling $super. The method that should be invoked when constructing
   *  new objects of this class should be provided in a property named
   *  construct, otherwise a default (which does nothing) will be used.
   *  Finally, $.isA will return true if an object of this class is tested 
   *  against a reference to one of its ancestor classes.
   * @return {Class} The newly created class
   * @see Class#$super
   * @since 0.1
   */
  newClass: function(definition) 
  {
    var theClass = function() { // the constructor
      function $super() { 
        this.superclass.apply(arguments);
      }
      this.construct.apply(this, arguments); 
    }, superclass = definition.superclass;      
    
    /**
     * The superclass of this class (or Object if none was provided)
     * @alias Class.superclass
     * @since 0.1
     */
    theClass.superclass  = superclass || Object;
    theClass.constructor = theClass;
    $.extend((theClass.prototype = superclass ? 
      superclass.prototype : {}), definition);
     
    $.addMethods(theClass, 
    {
      /**
       * Adds the functions in the passed object as methods of this class
       * @alias Class.prototype.addMethods
       * @param {Object} [methods] An object containing the methods to
       *  add (any properties that aren't functions in this object will
       *  be ignored)
       * @param {Boolean} [overwrite] If true, overwrites any methods
       *  that already exist in this class. The default is false.
       * @since 0.1
       */
      addMethods: function() {
        return $.addMethods.apply(null, [this].concat($.A(arguments)));
      },	
      
      /**
       * Function called from the constructor of this class whenever
       * an object of this class is created.
       * @alias Class.prototype.construct
       * @param {Object} [args] Zero or more arguments to pass to the
       *  construct method of your class, as appropriate.
       * @return The newly-constructed instance of this class
       * @since 0.1
       */
      construct: $.EmptyFn,
      
      /**
       * Shortcut method for invoking superclass methods that have
       * been overridden in this class.
       * @alias Class.prototype.$super
       * @param {String} [methodName] The name of the superclass's method to 
       *  invoke. If no such method exists in the superclass (or there is no
       *  superclass), this method does nothing. All additional arguments 
       *  will be passed untoched to the superclass's version of the method.
       * @return {Object} Whatever the superclass's version of the
       *  method specified happens to return.
       * @since 0.1
       */
      $super: function() {
        var args = $.A(arguments), 
            superclass = args.shift(),
            methodName = args.shift();  
                  
        return superclass.prototype[methodName].apply(this, args);
      }
    });
    
    return theClass;
  }
});
